package com.mosai.alignpro.util;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.core.app.ActivityCompat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.mosai.alignpro.R;
import com.mosai.alignpro.entity.Angle;
import com.mosai.alignpro.entity.BitmapInfo;
import com.mosai.alignpro.entity.ImageInfo;
import com.mosai.alignpro.entity.Point;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

/**
 * Created by Rays on 2020/3/9.
 */
public final class Utils {
    private static final String TAG = Utils.class.getSimpleName();

    public static boolean isUiThread() {
        return Looper.getMainLooper() == Looper.myLooper();
    }

    public static boolean isJson(String json) {
        return json != null && json.matches("^(\\{[\\s\\S]*\\})|(\\[[\\s\\S]*\\])$");
    }

    public static void setStatusBarHeight(Context context, View vStatusBar) {
        int statusBarHeight = Utils.getStatusBarHeight(context);
        ViewGroup.LayoutParams params = vStatusBar.getLayoutParams();
        params.height = statusBarHeight;
        vStatusBar.setLayoutParams(params);
    }

    public static int getStatusBarHeight(Context context) {
        Resources resources = context.getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        int height = resources.getDimensionPixelSize(resourceId);
        LogUtils.i(TAG, "getStatusBarHeight height: " + height + " density: " + resources.getDisplayMetrics().density);
        return height;
    }

    public static String getAngleText(ImageInfo imageInfo) {
        List<Angle> angles = imageInfo.getAngles();
        switch (angles.size()) {
            case 0:
                return "";
            case 1:
                return String.format(Locale.getDefault(), "TK=%.1f", angles.get(0).getAngle());
            case 2:
                Angle llAngle = angles.get(1);
                Angle ssAngle = new Angle();
                if (llAngle.getPoints().size() == 4) {
                    Point point1 = llAngle.getPoints().get(2);
                    ssAngle.add(point1);
                    Point point2 = llAngle.getPoints().get(3);
                    ssAngle.add(point2);
                    ssAngle.add(new Point(Arrays.asList(point1.getOriginX(), point2.getOriginY()), point1.getScale(), 0, 0, ssAngle));
                    ssAngle.add(new Point(Arrays.asList(point2.getOriginX(), point2.getOriginY()), point1.getScale(), 0, 0, ssAngle));
                    ssAngle.calculateAngle();
                }
                double tk = angles.get(0).getAngle();
                double ll = llAngle.getAngle();
                double ss = ssAngle.getAngle();

                Point circlePoint = imageInfo.getCirclePoint();
                if (circlePoint != null && circlePoint.getAngles() != null && circlePoint.getAngles().size() == 2) {
                    double pt = circlePoint.getAngles().get(0).getAngle();
                    double pi = circlePoint.getAngles().get(1).getAngle();
                    return String.format(Locale.getDefault(), "TK=%.1f\nLL=%.1f\nSS=%.1f\nPI=%.1f\nPT=%.1f", tk, ll, ss, pi, pt);
                }
                return String.format(Locale.getDefault(), "TK=%.1f\nLL=%.1f\nSS=%.1f", tk, ll, ss);
            default:
                return String.format(Locale.getDefault(), "TK=%.1f\nLL=%.1f\nSS=%.1f", angles.get(0).getAngle(), angles.get(1).getAngle(), angles.get(2).getAngle());
        }
    }

    public static int getConditionStatus(List<Angle> angles) {
        if (angles.isEmpty()) {
            return ImageInfo.CONDITION_STATUS_INIT;
        }
        double maxAngle = 0;
        for (Angle angle : angles) {
            if (angle.getAngle() > maxAngle) {
                maxAngle = angle.getAngle();
            }
        }
        if (maxAngle < 20) {
            return ImageInfo.CONDITION_STATUS_MILD;
        } else if (maxAngle <= 40) {
            return ImageInfo.CONDITION_STATUS_MODERATE;
        }
        return ImageInfo.CONDITION_STATUS_SEVERE;
    }

    public static String getConditionStatusText(int conditionStatus, Context context) {
        switch (conditionStatus) {
            case ImageInfo.CONDITION_STATUS_MILD:
                return context.getString(R.string.condition_status_mild);
            case ImageInfo.CONDITION_STATUS_MODERATE:
                return context.getString(R.string.condition_status_moderate);
            case ImageInfo.CONDITION_STATUS_SEVERE:
                return context.getString(R.string.condition_status_severe);
            default:
                return "";
        }
    }

    public static int severityToConditionStatus(String severity) {
        if (TextUtils.isEmpty(severity)) return 0;
        switch (severity) {
            case "Mild":
                return ImageInfo.CONDITION_STATUS_MILD;
            case "Moderate":
                return ImageInfo.CONDITION_STATUS_MODERATE;
            case "Severe":
                return ImageInfo.CONDITION_STATUS_SEVERE;
            default:
                return 0;
        }
    }

    public static String getDescribe(ImageInfo imageInfo, Context context) {
        if (imageInfo.isShowCondition()) {
            return getDescribe(imageInfo.getNewConditionStatus(), context);
        }
        if (TextUtils.isEmpty(imageInfo.getNewAngleText())) {
            return "";
        }
        return context.getString(R.string.curvature_describe_default);
    }

    public static String getDescribe(int conditionStatus, Context context) {
        switch (conditionStatus) {
            case ImageInfo.CONDITION_STATUS_MILD:
                return context.getString(R.string.condition_status_describe_mild);
            case ImageInfo.CONDITION_STATUS_MODERATE:
                return context.getString(R.string.condition_status_describe_moderate);
            case ImageInfo.CONDITION_STATUS_SEVERE:
                return context.getString(R.string.condition_status_describe_severe);
            default:
                return "";
        }
    }

    public static void setNewAIResult(ImageInfo info) {
        List<float[]> aiResult = new ArrayList<>();
        List<float[]> customResult = new ArrayList<>();

        if (info.getAngles() != null) {
            for (Angle angle : info.getAngles()) {
                for (Point point : angle.getPoints()) {
                    float x = point.getX() / point.getScale();
                    float y = point.getY() / point.getScale();
                    float[] floats = {x, y};
                    if (angle.isCustom()) {
                        customResult.add(floats);
                    } else {
                        aiResult.add(floats);
                    }
                }
            }
        }

        Point point = info.getCirclePoint();
        if (point != null) {
            float x = point.getX() / point.getScale();
            float y = point.getY() / point.getScale();
            float[] floats = {x, y};
            customResult.add(floats);
        }
        info.setNewAddCheckResultInfo(JSON.toJSONString(customResult));
        info.setNewAIResult(JSON.toJSONString(aiResult));
    }

    public static void saveImage(Context context, byte[] data, MediaScannerConnection.OnScanCompletedListener callback) {
        FileOutputStream fos = null;
        try {
            File destDir;
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                destDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            } else {
                destDir = new File(context.getFilesDir(), Environment.DIRECTORY_DCIM);
            }
            if (!destDir.exists() && !destDir.mkdirs()) {
                LogUtils.w(TAG, "saveImage mkdirs fail, path: " + destDir.getPath());
                return;
            }
            File imageFile = new File(destDir, System.currentTimeMillis() + ".jpg");
            if (!imageFile.createNewFile()) {
                LogUtils.w(TAG, "saveImage createNewFile fail, path: " + imageFile.getPath());
                return;
            }
            fos = new FileOutputStream(imageFile);
            fos.write(data, 0, data.length);

            MediaScannerConnection.scanFile(
                    context,
                    new String[]{imageFile.getAbsolutePath()},
                    null,
                    (path, uri) -> {
                        LogUtils.i(TAG, "saveImage path: " + path + " uri: " + uri);
                        callback.onScanCompleted(path, uri);
                    });
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static Uri getLatestPhoto(Activity activity) {
        if (Utils.checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
            return null;
        }
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        ContentResolver contentResolver = activity.getContentResolver();
        String[] projection = {
                MediaStore.Images.ImageColumns._ID
        };
        Cursor cursor;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Bundle bundle = new Bundle();
            bundle.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
            bundle.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, new String[]{MediaStore.Images.ImageColumns.DATE_ADDED});
            bundle.putInt(ContentResolver.QUERY_ARG_SORT_DIRECTION, ContentResolver.QUERY_SORT_DIRECTION_DESCENDING);
            cursor = contentResolver.query(uri, projection, bundle, null);
        } else {
            cursor = contentResolver.query(uri, projection, null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC LIMIT 1");
        }

        if (cursor != null) {
            int count = cursor.getCount();
            LogUtils.i(TAG, "getLatestPhoto count: " + count);
            if (count > 0 && cursor.moveToFirst()) {
                int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID));
                Uri latestUri = ContentUris.withAppendedId(uri, id);
                LogUtils.i(TAG, "getLatestPhoto"
                        + " id: " + id
                        + " latestUri: " + latestUri
                        + " authority: " + uri.getAuthority()
                );
                cursor.close();
                return latestUri;
            }
            cursor.close();
        }
        return null;
    }

    public static boolean checkPermission(Activity activity, String... permissions) {
        return checkRequestPermission(activity, null, permissions);
    }

    public static boolean checkRequestPermission(Activity activity, Integer requestCode, String... permissions) {
        List<String> reqList = new ArrayList<>(permissions.length);
        for (String permission : permissions) {
            if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                reqList.add(permission);
            }
        }
        if (!reqList.isEmpty()) {
            if (requestCode != null) {
                ActivityCompat.requestPermissions(activity, reqList.toArray(new String[]{}), requestCode);
            }
            return true;
        }
        return false;
    }

    public static double calculateLineAngle(List<Point> data, int index) {
        double angle1 = calculateAngle(data.get(index), data.get(index + 1));
        double angle2 = calculateAngle(data.get(index + 2), data.get(index + 3));
        double angle = Math.abs(angle1 - angle2);
//        LogI(TAG, "calculateLineAngle:"
//                + " angle1: " + angle1
//                + " angle2: " + angle2
//                + " angle: " + angle
//        );
        // 保留一位小数
        if (angle > 90) {
            angle = 180 - angle;
        }
        return new BigDecimal(angle).setScale(1, RoundingMode.HALF_UP).doubleValue();
    }

    private static double calculateAngle(Point point1, Point point2) {
        return calculateAngle(point1.getX(), point1.getY(), point2.getX(), point2.getY());
    }

    private static double calculateAngle(float x1, float y1, float x2, float y2) {
        if (x1 == x2) {
            return 90;
        } else if (y1 == y2) {
            return 0;
        }
        return Math.atan((y2 - y1) / (x2 - x1)) * 180 / Math.PI;
    }


    public static Point calculateCenterPoint(Point point1, Point point2, float length) {
        if (point1.getX() == point2.getX()) {
            return new Point(point1.getX() + length, (point1.getY() + point2.getY()) / 2.0f);
        } else if (point1.getY() == point2.getY()) {
            return new Point((point1.getX() + point2.getX()) / 2.0f, point1.getY() + length);
        }
        double angle = calculateAngle(point1, point2);
        double x = (point1.getX() + point2.getX()) / 2.0f - Math.sin(angle * Math.PI / 180) * length;
        double y = (point1.getY() + point2.getY()) / 2.0f + Math.cos(angle * Math.PI / 180) * length;
        return new Point((float) x, (float) y);
    }

    public static List<List<Float>> getResultList(String resultInfo) {
        if (TextUtils.isEmpty(resultInfo)) {
//            return null;
            return new ArrayList<>();
        }
        try {
            return JSON.parseObject(resultInfo, new TypeReference<List<List<Float>>>() {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ArrayList<>();
    }

    public static BitmapInfo getBitmap(InputStream is, int outWidth, int outHeight) {
        Bitmap bitmap = BitmapFactory.decodeStream(is);
        return getBitmapInfo(bitmap, outWidth, outHeight);
    }

    public static BitmapInfo getBitmapInfo(Bitmap bitmap, int outWidth, int outHeight) {
        return getBitmapInfo(bitmap, outWidth, outHeight, 1);
    }

    public static BitmapInfo getBitmapInfo(Bitmap bitmap, int outWidth, int outHeight, int inSampleSize) {
        if (bitmap == null) {
            return null;
        }
        LogUtils.i(TAG, "getBitmap: bitmap"
                + " width: " + bitmap.getWidth()
                + " height: " + bitmap.getHeight()
                + " outWidth: " + outWidth
                + " outHeight: " + outHeight
        );
        float wScale = 1f * outWidth / bitmap.getWidth();
        float hScale = 1f * outHeight / bitmap.getHeight();
        float scale = Math.min(wScale, hScale);
        float originScale = scale / inSampleSize;
        LogUtils.i(TAG, "getBitmap:"
                + " wScale: " + wScale
                + " hScale: " + hScale
                + " scale: " + scale
                + " originScale: " + originScale
        );
        return new BitmapInfo(bitmap, scale, originScale);
    }

    public static BitmapInfo getBitmap(String path, int outWidth, int outHeight) {
        File externalStorageDirectory = Environment.getExternalStorageDirectory();
        File file = new File(externalStorageDirectory, path);
        LogUtils.i(TAG, "refresh: path: " + file.getAbsolutePath());
        if (file.exists()) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true; // inJustDecodeBounds为true，不返回bitmap，只返回这个bitmap的尺寸
            BitmapFactory.decodeFile(file.getAbsolutePath(), options);
            // 利用返回的原图片的宽高，我们就可以计算出缩放比inSampleSize（只能是2的整数次幂）
            int inSampleSize = calculateSampleSize(options, outWidth, outHeight);
            options.inSampleSize = inSampleSize;
            options.inPreferredConfig = Bitmap.Config.RGB_565; // 使用RGB_565减少图片大小
            options.inJustDecodeBounds = false; // inJustDecodeBounds为false，返回bitmap

            Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
            return getBitmapInfo(bitmap, outWidth, outHeight, inSampleSize);
        }
        return null;
    }

    private static int calculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int inSampleSize = 1;
        if (reqWidth > 0 && reqHeight > 0) {
            int halfWidth = options.outWidth >> 1;
            int halfHeight = options.outHeight >> 1;
            while ((halfWidth / inSampleSize) >= reqWidth && (halfHeight / inSampleSize) >= reqHeight) {
                inSampleSize *= 2;
            }
        }
        LogUtils.i(TAG, "calculateSampleSize: options "
                + " outWidth: " + options.outWidth
                + " outHeight: " + options.outHeight
                + " inPreferredConfig: " + options.inPreferredConfig
                + " reqWidth: " + reqWidth
                + " reqHeight: " + reqHeight
                + " inSampleSize: " + inSampleSize
        );
        return inSampleSize;
    }

    public static boolean hasAngleInfo(ImageInfo imageInfo) {
        List<List<Float>> data = Utils.getResultList(imageInfo.getCheckResultInfo());
        if (data.size() >= 4) {
            return true;
        }
        List<List<Float>> custom = Utils.getResultList(imageInfo.getAddCheckResultInfo());
        return custom.size() >= 4;
    }

    public static void setImageTypeText(TextView textView, ImageInfo imageInfo) {
        try {
            Context context = textView.getContext();
            textView.setText(context.getResources().getIdentifier("image_type_" + imageInfo.getImageType(), "string", context.getPackageName()));
            textView.setVisibility(View.VISIBLE);
        } catch (Exception e) {
            e.printStackTrace();
            textView.setVisibility(View.GONE);
        }
    }

    public static boolean setScoliosisTypeText(TextView textView, ImageInfo imageInfo) {
        try {
            if (imageInfo.getImageType() != ImageInfo.IMAGE_TYPE_BACK) return false;
            Context context = textView.getContext();
            textView.setText(context.getResources().getIdentifier("scoliosis_type_" + imageInfo.getNewScoliosisType(), "string", context.getPackageName()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public static int indexOf(Object[] array, Object data) {
        if (array == null) return -1;
        for (int i = 0; i < array.length; i++) {
            if (Objects.equals(array[i], data)) {
                return i;
            }
        }
        return -1;
    }
}
